Skip to content

Commit f3b8e7a

Browse files
authored
Merge pull request #20 from willmcgugan/grid
New Layout system
2 parents a797c07 + fe288e8 commit f3b8e7a

27 files changed

+1283
-601
lines changed

examples/simple.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,37 @@
22

33
from textual import events
44
from textual.app import App
5-
from textual.widgets.header import Header
6-
from textual.widgets.placeholder import Placeholder
7-
from textual.widgets.scroll_view import ScrollView
8-
9-
with open("richreadme.md", "rt") as fh:
10-
readme = Markdown(fh.read(), hyperlinks=True, code_theme="fruity")
5+
from textual.view import DockView
6+
from textual.widgets import Header, Footer, Placeholder, ScrollView
117

128

139
class MyApp(App):
10+
"""An example of a very simple Textual App"""
1411

15-
KEYS = {"q": "quit", "ctrl+c": "quit", "b": "view.toggle('left')"}
12+
async def on_load(self, event: events.Load) -> None:
13+
await self.bind("q,ctrl+c", "quit")
14+
await self.bind("b", "view.toggle('sidebar')")
1615

1716
async def on_startup(self, event: events.Startup) -> None:
18-
await self.view.mount_all(
19-
header=Header(self.title), left=Placeholder(), body=ScrollView(readme)
20-
)
17+
view = await self.push_view(DockView())
18+
header = Header(self.title)
19+
footer = Footer()
20+
sidebar = Placeholder(name="sidebar")
21+
22+
with open("richreadme.md", "rt") as fh:
23+
readme = Markdown(fh.read(), hyperlinks=True)
24+
25+
body = ScrollView(readme)
26+
27+
footer.add_key("b", "Toggle sidebar")
28+
footer.add_key("q", "Quit")
29+
30+
await view.dock(header, edge="top")
31+
await view.dock(footer, edge="bottom")
32+
await view.dock(sidebar, edge="left", size=30)
33+
await view.dock(body, edge="right")
34+
self.require_layout()
2135

2236

23-
app = MyApp()
37+
app = MyApp(title="Simple App")
2438
app.run()

poetry.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/textual/_animator.py

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,32 @@
33
import logging
44

55
import asyncio
6+
import sys
67
from time import time
78
from tracemalloc import start
8-
from typing import Callable
9+
from typing import Callable, TypeVar
910

1011
from dataclasses import dataclass
1112

1213
from ._timer import Timer
1314
from ._types import MessageTarget
1415

16+
if sys.version_info >= (3, 8):
17+
from typing import Protocol
18+
else:
19+
from typing_extensions import Protocol
20+
1521

1622
EasingFunction = Callable[[float], float]
1723

24+
T = TypeVar("T")
25+
26+
27+
class Animatable(Protocol):
28+
def blend(self: T, destination: T, factor: float) -> T:
29+
...
30+
31+
1832
# https://easings.net/
1933
EASING = {
2034
"none": lambda x: 1.0,
@@ -25,6 +39,8 @@
2539
"out_cubic": lambda x: 1 - pow(1 - x, 3),
2640
}
2741

42+
DEFAULT_EASING = "in_out_cubic"
43+
2844

2945
log = logging.getLogger("rich")
3046

@@ -35,30 +51,43 @@ class Animation:
3551
attribute: str
3652
start_time: float
3753
duration: float
38-
start_value: float
39-
end_value: float
54+
start_value: float | Animatable
55+
end_value: float | Animatable
4056
easing_function: EasingFunction
4157

4258
def __call__(self, time: float) -> bool:
59+
def blend_float(start: float, end: float, factor: float) -> float:
60+
return start + (end - start) * factor
61+
62+
AnimatableT = TypeVar("AnimatableT", bound=Animatable)
63+
64+
def blend(start: AnimatableT, end: AnimatableT, factor: float) -> AnimatableT:
65+
return start.blend(end, factor)
66+
67+
blend_function = (
68+
blend_float if isinstance(self.start_value, (int, float)) else blend
69+
)
4370

4471
if self.duration == 0:
4572
value = self.end_value
4673
else:
47-
progress = min(1.0, (time - self.start_time) / self.duration)
74+
factor = min(1.0, (time - self.start_time) / self.duration)
75+
eased_factor = self.easing_function(factor)
76+
# value = blend_function(self.start_value, self.end_value, eased_factor)
77+
4878
if self.end_value > self.start_value:
49-
eased_progress = self.easing_function(progress)
79+
eased_factor = self.easing_function(factor)
5080
value = (
5181
self.start_value
52-
+ (self.end_value - self.start_value) * eased_progress
82+
+ (self.end_value - self.start_value) * eased_factor
5383
)
5484
else:
55-
eased_progress = 1 - self.easing_function(progress)
85+
eased_factor = 1 - self.easing_function(factor)
5686
value = (
57-
self.end_value
58-
+ (self.start_value - self.end_value) * eased_progress
87+
self.end_value + (self.start_value - self.end_value) * eased_factor
5988
)
60-
6189
setattr(self.obj, self.attribute, value)
90+
log.debug("ANIMATE %r %r -> %r", self.obj, self.attribute, value)
6291
return value == self.end_value
6392

6493

@@ -74,7 +103,7 @@ def __call__(
74103
*,
75104
duration: float | None = None,
76105
speed: float | None = None,
77-
easing: EasingFunction | str = "in_out_cubic",
106+
easing: EasingFunction | str = DEFAULT_EASING,
78107
) -> None:
79108
easing_function = EASING[easing] if isinstance(easing, str) else easing
80109
self._animator.animate(
@@ -88,7 +117,7 @@ def __call__(
88117

89118

90119
class Animator:
91-
def __init__(self, target: MessageTarget, frames_per_second: int = 30) -> None:
120+
def __init__(self, target: MessageTarget, frames_per_second: int = 60) -> None:
92121
self._animations: dict[tuple[object, str], Animation] = {}
93122
self._timer = Timer(target, 1 / frames_per_second, target, callback=self)
94123

@@ -109,7 +138,7 @@ def animate(
109138
*,
110139
duration: float | None = None,
111140
speed: float | None = None,
112-
easing: EasingFunction = EASING["in_out_cubic"],
141+
easing: EasingFunction | str = DEFAULT_EASING,
113142
) -> None:
114143

115144
start_time = time()
@@ -123,15 +152,15 @@ def animate(
123152
animation_duration = duration
124153
else:
125154
animation_duration = abs(value - start_value) / (speed or 50)
126-
155+
easing_function = EASING[easing] if isinstance(easing, str) else easing
127156
animation = Animation(
128157
obj,
129158
attribute=attribute,
130159
start_time=start_time,
131160
duration=animation_duration,
132161
start_value=start_value,
133162
end_value=value,
134-
easing_function=easing,
163+
easing_function=easing_function,
135164
)
136165
self._animations[animation_key] = animation
137166
self._timer.resume()

src/textual/_timer.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def __init__(
3030
name: str | None = None,
3131
callback: TimerCallback | None = None,
3232
repeat: int = None,
33+
skip: bool = True,
3334
) -> None:
3435
self._target_repr = repr(event_target)
3536
self._target = weakref.ref(event_target)
@@ -39,6 +40,7 @@ def __init__(
3940
self._timer_count += 1
4041
self._callback = callback
4142
self._repeat = repeat
43+
self._skip = skip
4244
self._stop_event = Event()
4345
self._active = Event()
4446
self._active.set()
@@ -75,7 +77,11 @@ async def run(self) -> None:
7577
try:
7678
while _repeat is None or count <= _repeat:
7779
next_timer = start + (count * _interval)
80+
if self._skip and next_timer < monotonic():
81+
count += 1
82+
continue
7883
try:
84+
7985
if await wait_for(_wait(), max(0, next_timer - monotonic())):
8086
break
8187
except TimeoutError:

src/textual/_xterm_parser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def parse_mouse_code(self, code: str, sender: MessageTarget) -> events.Event | N
3131
if sgr_match:
3232
_buttons, _x, _y, state = sgr_match.groups()
3333
buttons = int(_buttons)
34-
button = buttons & 3
34+
button = (buttons + 1) & 3
3535
x = int(_x) - 1
3636
y = int(_y) - 1
3737
delta_x = x - self.last_x
@@ -41,7 +41,7 @@ def parse_mouse_code(self, code: str, sender: MessageTarget) -> events.Event | N
4141
event: events.Event
4242
if buttons & 64:
4343
event = (
44-
events.MouseScrollUp if button == 1 else events.MouseScrollDown
44+
events.MouseScrollDown if button == 1 else events.MouseScrollUp
4545
)(sender, x, y)
4646
else:
4747
event = (

0 commit comments

Comments
 (0)