mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
examples working
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
from rich.align import Align
|
||||
from rich.console import Console, ConsoleOptions, RenderResult
|
||||
from rich.padding import Padding
|
||||
from rich.text import Text
|
||||
|
||||
from textual.app import App
|
||||
@@ -14,10 +13,10 @@ try:
|
||||
except ImportError:
|
||||
print("Please install pyfiglet to run this example")
|
||||
|
||||
font = Figlet(font="small")
|
||||
|
||||
|
||||
class FigletText:
|
||||
"""A renderable to generate figlet text that adapts to fit the container."""
|
||||
|
||||
def __init__(self, text: str) -> None:
|
||||
self.text = text
|
||||
|
||||
@@ -27,25 +26,22 @@ class FigletText:
|
||||
size = min(options.max_width / 2, options.max_height)
|
||||
|
||||
text = self.text
|
||||
if size <= 4:
|
||||
if size < 4:
|
||||
yield Text(text, style="bold")
|
||||
return
|
||||
if size < 6:
|
||||
font_name = "mini"
|
||||
elif size < 8:
|
||||
font_name = "small"
|
||||
elif size < 10:
|
||||
font_name = "standard"
|
||||
else:
|
||||
font_name = "big"
|
||||
font = Figlet(font=font_name)
|
||||
yield Text(font.renderText(text).rstrip("\n"), style="bold")
|
||||
if size < 7:
|
||||
font_name = "mini"
|
||||
elif size < 8:
|
||||
font_name = "small"
|
||||
elif size < 10:
|
||||
font_name = "standard"
|
||||
else:
|
||||
font_name = "big"
|
||||
font = Figlet(font=font_name)
|
||||
yield Text(font.renderText(text).rstrip("\n"), style="bold")
|
||||
|
||||
|
||||
class CalculatorApp(App):
|
||||
async def on_load(self, event: events.Load) -> None:
|
||||
await self.bind("q,ctrl+c", "quit", "Quit")
|
||||
|
||||
async def on_startup(self, event: events.Startup) -> None:
|
||||
|
||||
layout = GridLayout(gap=(2, 1), gutter=1, align=("center", "center"))
|
||||
@@ -74,7 +70,7 @@ class CalculatorApp(App):
|
||||
|
||||
layout.place(
|
||||
*buttons.values(),
|
||||
numbers=Static(Padding(numbers, (0, 1), style="white on rgb(51,51,51)")),
|
||||
numbers=Static(numbers, padding=(0, 1), style="white on rgb(51,51,51)"),
|
||||
zero=make_button("0"),
|
||||
)
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ class GridTest(App):
|
||||
layout.add_row("row", fraction=1, max_size=10)
|
||||
layout.set_repeat(True, True)
|
||||
layout.add_areas(center="col-2-start|col-4-end,row-2-start|row-3-end")
|
||||
layout.set_align("center", "center")
|
||||
layout.set_align("stretch", "center")
|
||||
|
||||
# *(Placeholder() for _ in range(20)),
|
||||
layout.place(*(Placeholder() for _ in range(20)), center=Placeholder())
|
||||
|
||||
@@ -9,16 +9,6 @@ from textual.views import DockView
|
||||
from textual.widgets import Header, Footer, Placeholder, ScrollView
|
||||
|
||||
|
||||
logging.basicConfig(
|
||||
level="NOTSET",
|
||||
format="%(message)s",
|
||||
datefmt="[%X]",
|
||||
handlers=[FileHandler("richtui.log")],
|
||||
)
|
||||
|
||||
log = logging.getLogger("rich")
|
||||
|
||||
|
||||
class MyApp(App):
|
||||
"""An example of a very simple Textual App"""
|
||||
|
||||
@@ -31,12 +21,13 @@ class MyApp(App):
|
||||
view = await self.push_view(DockView())
|
||||
|
||||
footer = Footer()
|
||||
footer.add_key("b", "Toggle sidebar")
|
||||
footer.add_key("q", "Quit")
|
||||
header = Header()
|
||||
body = ScrollView()
|
||||
sidebar = Placeholder()
|
||||
|
||||
footer.add_key("b", "Toggle sidebar")
|
||||
footer.add_key("q", "Quit")
|
||||
|
||||
await view.dock(header, edge="top")
|
||||
await view.dock(footer, edge="bottom")
|
||||
await view.dock(sidebar, edge="left", size=30, name="sidebar")
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 231 KiB After Width: | Height: | Size: 568 KiB |
@@ -75,5 +75,6 @@ def layout_resolve(total: int, edges: Sequence[Edge]) -> List[int]:
|
||||
size, remainder = divmod(portion * edge.fraction + remainder, 1)
|
||||
sizes[index] = size
|
||||
break
|
||||
|
||||
# Sizes now contains integers only
|
||||
return cast(List[int], sizes)
|
||||
|
||||
@@ -96,6 +96,9 @@ class App(MessagePump):
|
||||
self.mouse_position = Point(0, 0)
|
||||
self.bindings = Bindings()
|
||||
self._title = title
|
||||
|
||||
self.bindings.bind("ctrl+c", "quit")
|
||||
|
||||
super().__init__()
|
||||
|
||||
title: Reactive[str] = Reactive("Textual")
|
||||
@@ -252,6 +255,7 @@ class App(MessagePump):
|
||||
self.view.require_layout()
|
||||
|
||||
async def call_later(self, callback: Callable, *args, **kwargs) -> None:
|
||||
await self.post_message(events.Idle(self))
|
||||
await self.post_message(
|
||||
events.Callback(self, partial(callback, *args, **kwargs))
|
||||
)
|
||||
|
||||
@@ -29,7 +29,7 @@ class Event(Message):
|
||||
|
||||
|
||||
class Null(Event):
|
||||
def can_batch(self, message: Message) -> bool:
|
||||
def can_replace(self, message: Message) -> bool:
|
||||
return isinstance(message, Null)
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ class Resize(Event):
|
||||
self.height = height
|
||||
super().__init__(sender)
|
||||
|
||||
def can_batch(self, message: "Message") -> bool:
|
||||
def can_replace(self, message: "Message") -> bool:
|
||||
return isinstance(message, Resize)
|
||||
|
||||
@property
|
||||
@@ -324,5 +324,5 @@ class Blur(Event):
|
||||
|
||||
|
||||
class Update(Event):
|
||||
def can_batch(self, event: Message) -> bool:
|
||||
def can_replace(self, event: Message) -> bool:
|
||||
return isinstance(event, Update) and event.sender == self.sender
|
||||
|
||||
@@ -268,8 +268,6 @@ class GridLayout(Layout):
|
||||
break
|
||||
yield total, total + track
|
||||
total += track + gap
|
||||
# if index >= edge_count:
|
||||
# break
|
||||
|
||||
def resolve_tracks(
|
||||
grid: list[GridOptions], size: int, gap: int, repeat: bool
|
||||
@@ -328,7 +326,6 @@ class GridLayout(Layout):
|
||||
self.row_gap,
|
||||
self.row_repeat,
|
||||
)
|
||||
log.debug("%s", column_names)
|
||||
grid_size = Dimensions(column_size, row_size)
|
||||
|
||||
widget_areas = (
|
||||
|
||||
@@ -37,7 +37,7 @@ class Message:
|
||||
super().__init_subclass__()
|
||||
cls.bubble = bubble
|
||||
|
||||
def can_batch(self, message: "Message") -> bool:
|
||||
def can_replace(self, message: "Message") -> bool:
|
||||
"""Check if another message may supersede this one.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -152,7 +152,7 @@ class MessagePump:
|
||||
# Combine any pending messages that may supersede this one
|
||||
while not (self._closed or self._closing):
|
||||
pending = self.peek_message()
|
||||
if pending is None or not message.can_batch(pending):
|
||||
if pending is None or not message.can_replace(pending):
|
||||
break
|
||||
try:
|
||||
message = await self.get_message()
|
||||
|
||||
@@ -34,11 +34,11 @@ class UpdateMessage(Message):
|
||||
yield "offset_y", self.offset_y, 0
|
||||
yield "reflow", self.reflow, False
|
||||
|
||||
def can_batch(self, message: Message) -> bool:
|
||||
def can_replace(self, message: Message) -> bool:
|
||||
return isinstance(message, UpdateMessage) and message.sender == self.sender
|
||||
|
||||
|
||||
@rich.repr.auto
|
||||
class LayoutMessage(Message):
|
||||
def can_batch(self, message: Message) -> bool:
|
||||
def can_replace(self, message: Message) -> bool:
|
||||
return isinstance(message, LayoutMessage) and message.sender == self.sender
|
||||
|
||||
@@ -16,7 +16,7 @@ log = getLogger("rich")
|
||||
|
||||
|
||||
class PageUpdate(Message):
|
||||
def can_batch(self, message: "Message") -> bool:
|
||||
def can_replace(self, message: "Message") -> bool:
|
||||
return isinstance(message, PageUpdate)
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ class PageRender:
|
||||
|
||||
def render(self, console: Console, options: ConsoleOptions) -> None:
|
||||
width = self.width or options.max_width or console.width
|
||||
height = self.height or options.height or None
|
||||
width *= 2
|
||||
options = options.update_dimensions(width, None)
|
||||
style = console.get_style(self.style)
|
||||
renderable = self.renderable
|
||||
@@ -77,6 +77,14 @@ class PageRender:
|
||||
x, y = self.offset
|
||||
window_lines = self._lines[y : y + height]
|
||||
|
||||
if x:
|
||||
|
||||
def width_view(line: list[Segment]) -> list[Segment]:
|
||||
_, line = Segment.divide(line, [x, x + width])
|
||||
return line
|
||||
|
||||
window_lines = [width_view(line) for line in window_lines]
|
||||
|
||||
missing_lines = len(window_lines) - height
|
||||
if missing_lines:
|
||||
blank_line = [Segment(" " * width, style), Segment.line()]
|
||||
@@ -102,9 +110,16 @@ class Page(Widget):
|
||||
def contents_size(self) -> Dimensions:
|
||||
return self._page.size
|
||||
|
||||
def validate_x(self, value: int) -> int:
|
||||
return max(0, value)
|
||||
|
||||
def validate_y(self, value: int) -> int:
|
||||
return max(0, value)
|
||||
|
||||
async def watch_x(self, new: int) -> None:
|
||||
x, y = self._page.offset
|
||||
self._page.offset = Point(new, y)
|
||||
|
||||
async def watch_y(self, new: int) -> None:
|
||||
x, y = self._page.offset
|
||||
self._page.offset = Point(x, new)
|
||||
|
||||
@@ -28,6 +28,16 @@ class ScrollDown(Message, bubble=True):
|
||||
"""Message sent when clicking below handle."""
|
||||
|
||||
|
||||
@rich.repr.auto
|
||||
class ScrollLeft(Message, bubble=True):
|
||||
"""Message sent when clicking above handle."""
|
||||
|
||||
|
||||
@rich.repr.auto
|
||||
class ScrollRight(Message, bubble=True):
|
||||
"""Message sent when clicking below handle."""
|
||||
|
||||
|
||||
@rich.repr.auto
|
||||
class ScrollTo(Message, bubble=True):
|
||||
"""Message sent when click and dragging handle."""
|
||||
@@ -84,7 +94,7 @@ class ScrollBarRender:
|
||||
if ascii_only:
|
||||
bars = ["-", "-", "-", "-", "-", "-", "-", "-"]
|
||||
else:
|
||||
bars = ["▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"]
|
||||
bars = ["█", "▉", "▊", "▋", "▌", "▍", "▎", "▏"]
|
||||
|
||||
back = back_color
|
||||
bar = bar_color
|
||||
@@ -207,10 +217,10 @@ class ScrollBar(Widget):
|
||||
self.mouse_over = False
|
||||
|
||||
async def action_scroll_down(self) -> None:
|
||||
await self.emit(ScrollDown(self))
|
||||
await self.emit(ScrollDown(self) if self.vertical else ScrollRight(self))
|
||||
|
||||
async def action_scroll_up(self) -> None:
|
||||
await self.emit(ScrollUp(self))
|
||||
await self.emit(ScrollUp(self) if self.vertical else ScrollLeft(self))
|
||||
|
||||
async def action_grab(self) -> None:
|
||||
await self.capture_mouse()
|
||||
|
||||
@@ -40,7 +40,7 @@ class ScrollView(View):
|
||||
layout.add_areas(
|
||||
content="main,main", vertical="vertical,main", horizontal="main,horizontal"
|
||||
)
|
||||
layout.hide_row("horizontal")
|
||||
# layout.hide_row("horizontal")
|
||||
super().__init__(name=name, layout=layout)
|
||||
|
||||
x: Reactive[float] = Reactive(0)
|
||||
@@ -49,12 +49,22 @@ class ScrollView(View):
|
||||
target_x: Reactive[float] = Reactive(0)
|
||||
target_y: Reactive[float] = Reactive(0)
|
||||
|
||||
def validate_x(self, value: float) -> float:
|
||||
return clamp(value, 0, self._page.contents_size.width - self.size.width)
|
||||
|
||||
def validate_target_x(self, value: float) -> float:
|
||||
return clamp(value, 0, self._page.contents_size.width - self.size.width)
|
||||
|
||||
def validate_y(self, value: float) -> float:
|
||||
return clamp(value, 0, self._page.contents_size.height - self.size.height)
|
||||
|
||||
def validate_target_y(self, value: float) -> float:
|
||||
return clamp(value, 0, self._page.contents_size.height - self.size.height)
|
||||
|
||||
async def watch_x(self, new_value: float) -> None:
|
||||
self._page.x = round(new_value)
|
||||
self._horizontal_scrollbar.position = round(new_value)
|
||||
|
||||
async def watch_y(self, new_value: float) -> None:
|
||||
self._page.y = round(new_value)
|
||||
self._vertical_scrollbar.position = round(new_value)
|
||||
@@ -71,8 +81,6 @@ class ScrollView(View):
|
||||
horizontal=self._horizontal_scrollbar,
|
||||
)
|
||||
await self.layout.mount_all(self)
|
||||
# await self.dock(self._vertical_scrollbar, edge="right", size=1)
|
||||
# await self.dock(self._page, edge="top")
|
||||
|
||||
def scroll_up(self) -> None:
|
||||
self.target_y += 1.5
|
||||
@@ -90,6 +98,14 @@ class ScrollView(View):
|
||||
self.target_y += self.size.height
|
||||
self.animate("y", self.target_y, easing="out_cubic")
|
||||
|
||||
def page_left(self) -> None:
|
||||
self.target_x -= self.size.width
|
||||
self.animate("x", self.target_x, speed=120, easing="out_cubic")
|
||||
|
||||
def page_right(self) -> None:
|
||||
self.target_x += self.size.width
|
||||
self.animate("x", self.target_x, speed=120, easing="out_cubic")
|
||||
|
||||
async def on_mouse_scroll_up(self, event: events.MouseScrollUp) -> None:
|
||||
self.scroll_up()
|
||||
|
||||
@@ -116,11 +132,15 @@ class ScrollView(View):
|
||||
self.animate("y", self.target_y, easing="out_cubic")
|
||||
|
||||
async def key_end(self) -> None:
|
||||
self.target_x = 0
|
||||
self.target_y = self._page.contents_size.height - self.size.height
|
||||
self.animate("x", self.target_x, duration=1, easing="out_cubic")
|
||||
self.animate("y", self.target_y, duration=1, easing="out_cubic")
|
||||
|
||||
async def key_home(self) -> None:
|
||||
self.target_x = 0
|
||||
self.target_y = 0
|
||||
self.animate("x", self.target_x, duration=1, easing="out_cubic")
|
||||
self.animate("y", self.target_y, duration=1, easing="out_cubic")
|
||||
|
||||
async def on_resize(self, event: events.Resize) -> None:
|
||||
@@ -134,14 +154,24 @@ class ScrollView(View):
|
||||
async def message_scroll_down(self, message: Message) -> None:
|
||||
self.page_down()
|
||||
|
||||
async def message_scroll_left(self, message: Message) -> None:
|
||||
self.page_left()
|
||||
|
||||
async def message_scroll_right(self, message: Message) -> None:
|
||||
self.page_right()
|
||||
|
||||
async def message_scroll_to(self, message: ScrollTo) -> None:
|
||||
if message.x is not None:
|
||||
self.target_x = message.x
|
||||
if message.y is not None:
|
||||
self.target_y = message.y
|
||||
self.animate("x", self.target_x, speed=150, easing="out_cubic")
|
||||
self.animate("y", self.target_y, speed=150, easing="out_cubic")
|
||||
|
||||
async def message_page_update(self, message: Message) -> None:
|
||||
self.x = self.validate_x(self.x)
|
||||
self.y = self.validate_y(self.y)
|
||||
self._horizontal_scrollbar.virtual_size = self._page.virtual_size.width
|
||||
self._horizontal_scrollbar.window_size = self.size.width
|
||||
self._vertical_scrollbar.virtual_size = self._page.virtual_size.height
|
||||
self._vertical_scrollbar.window_size = self.size.height
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from rich.console import RenderableType
|
||||
from rich.padding import Padding, PaddingDimensions
|
||||
from rich.style import StyleType
|
||||
from rich.styled import Styled
|
||||
from ..widget import Widget
|
||||
@@ -8,14 +9,22 @@ from ..widget import Widget
|
||||
|
||||
class Static(Widget):
|
||||
def __init__(
|
||||
self, renderable: RenderableType, name: str | None = None, style: StyleType = ""
|
||||
self,
|
||||
renderable: RenderableType,
|
||||
name: str | None = None,
|
||||
style: StyleType = "",
|
||||
padding: PaddingDimensions = 0,
|
||||
) -> None:
|
||||
super().__init__(name)
|
||||
self.renderable = renderable
|
||||
self.style = style
|
||||
self.padding = padding
|
||||
|
||||
def render(self) -> RenderableType:
|
||||
return Styled(self.renderable, self.style)
|
||||
renderable = self.renderable
|
||||
if self.padding:
|
||||
renderable = Padding(renderable, self.padding)
|
||||
return Styled(renderable, self.style)
|
||||
|
||||
async def update(self, renderable: RenderableType) -> None:
|
||||
self.renderable = renderable
|
||||
|
||||
Reference in New Issue
Block a user