examples working

This commit is contained in:
Will McGugan
2021-07-12 19:07:50 +01:00
parent 6fc68424d6
commit fccdcaad2e
15 changed files with 104 additions and 51 deletions

View File

@@ -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"),
)

View File

@@ -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())

View File

@@ -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

View File

@@ -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)

View File

@@ -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))
)

View File

@@ -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

View File

@@ -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 = (

View File

@@ -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:

View File

@@ -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()

View File

@@ -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

View File

@@ -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)

View File

@@ -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()

View File

@@ -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

View File

@@ -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