Skip to content

Commit

Permalink
Merge pull request #20 from willmcgugan/grid
Browse files Browse the repository at this point in the history
New Layout system
  • Loading branch information
willmcgugan authored Jul 5, 2021
2 parents a797c07 + fe288e8 commit f3b8e7a
Show file tree
Hide file tree
Showing 27 changed files with 1,283 additions and 601 deletions.
36 changes: 25 additions & 11 deletions examples/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,37 @@

from textual import events
from textual.app import App
from textual.widgets.header import Header
from textual.widgets.placeholder import Placeholder
from textual.widgets.scroll_view import ScrollView

with open("richreadme.md", "rt") as fh:
readme = Markdown(fh.read(), hyperlinks=True, code_theme="fruity")
from textual.view import DockView
from textual.widgets import Header, Footer, Placeholder, ScrollView


class MyApp(App):
"""An example of a very simple Textual App"""

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

async def on_startup(self, event: events.Startup) -> None:
await self.view.mount_all(
header=Header(self.title), left=Placeholder(), body=ScrollView(readme)
)
view = await self.push_view(DockView())
header = Header(self.title)
footer = Footer()
sidebar = Placeholder(name="sidebar")

with open("richreadme.md", "rt") as fh:
readme = Markdown(fh.read(), hyperlinks=True)

body = ScrollView(readme)

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)
await view.dock(body, edge="right")
self.require_layout()


app = MyApp()
app = MyApp(title="Simple App")
app.run()
4 changes: 2 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 44 additions & 15 deletions src/textual/_animator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,32 @@
import logging

import asyncio
import sys
from time import time
from tracemalloc import start
from typing import Callable
from typing import Callable, TypeVar

from dataclasses import dataclass

from ._timer import Timer
from ._types import MessageTarget

if sys.version_info >= (3, 8):
from typing import Protocol
else:
from typing_extensions import Protocol


EasingFunction = Callable[[float], float]

T = TypeVar("T")


class Animatable(Protocol):
def blend(self: T, destination: T, factor: float) -> T:
...


# https://easings.net/
EASING = {
"none": lambda x: 1.0,
Expand All @@ -25,6 +39,8 @@
"out_cubic": lambda x: 1 - pow(1 - x, 3),
}

DEFAULT_EASING = "in_out_cubic"


log = logging.getLogger("rich")

Expand All @@ -35,30 +51,43 @@ class Animation:
attribute: str
start_time: float
duration: float
start_value: float
end_value: float
start_value: float | Animatable
end_value: float | Animatable
easing_function: EasingFunction

def __call__(self, time: float) -> bool:
def blend_float(start: float, end: float, factor: float) -> float:
return start + (end - start) * factor

AnimatableT = TypeVar("AnimatableT", bound=Animatable)

def blend(start: AnimatableT, end: AnimatableT, factor: float) -> AnimatableT:
return start.blend(end, factor)

blend_function = (
blend_float if isinstance(self.start_value, (int, float)) else blend
)

if self.duration == 0:
value = self.end_value
else:
progress = min(1.0, (time - self.start_time) / self.duration)
factor = min(1.0, (time - self.start_time) / self.duration)
eased_factor = self.easing_function(factor)
# value = blend_function(self.start_value, self.end_value, eased_factor)

if self.end_value > self.start_value:
eased_progress = self.easing_function(progress)
eased_factor = self.easing_function(factor)
value = (
self.start_value
+ (self.end_value - self.start_value) * eased_progress
+ (self.end_value - self.start_value) * eased_factor
)
else:
eased_progress = 1 - self.easing_function(progress)
eased_factor = 1 - self.easing_function(factor)
value = (
self.end_value
+ (self.start_value - self.end_value) * eased_progress
self.end_value + (self.start_value - self.end_value) * eased_factor
)

setattr(self.obj, self.attribute, value)
log.debug("ANIMATE %r %r -> %r", self.obj, self.attribute, value)
return value == self.end_value


Expand All @@ -74,7 +103,7 @@ def __call__(
*,
duration: float | None = None,
speed: float | None = None,
easing: EasingFunction | str = "in_out_cubic",
easing: EasingFunction | str = DEFAULT_EASING,
) -> None:
easing_function = EASING[easing] if isinstance(easing, str) else easing
self._animator.animate(
Expand All @@ -88,7 +117,7 @@ def __call__(


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

Expand All @@ -109,7 +138,7 @@ def animate(
*,
duration: float | None = None,
speed: float | None = None,
easing: EasingFunction = EASING["in_out_cubic"],
easing: EasingFunction | str = DEFAULT_EASING,
) -> None:

start_time = time()
Expand All @@ -123,15 +152,15 @@ def animate(
animation_duration = duration
else:
animation_duration = abs(value - start_value) / (speed or 50)

easing_function = EASING[easing] if isinstance(easing, str) else easing
animation = Animation(
obj,
attribute=attribute,
start_time=start_time,
duration=animation_duration,
start_value=start_value,
end_value=value,
easing_function=easing,
easing_function=easing_function,
)
self._animations[animation_key] = animation
self._timer.resume()
Expand Down
6 changes: 6 additions & 0 deletions src/textual/_timer.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def __init__(
name: str | None = None,
callback: TimerCallback | None = None,
repeat: int = None,
skip: bool = True,
) -> None:
self._target_repr = repr(event_target)
self._target = weakref.ref(event_target)
Expand All @@ -39,6 +40,7 @@ def __init__(
self._timer_count += 1
self._callback = callback
self._repeat = repeat
self._skip = skip
self._stop_event = Event()
self._active = Event()
self._active.set()
Expand Down Expand Up @@ -75,7 +77,11 @@ async def run(self) -> None:
try:
while _repeat is None or count <= _repeat:
next_timer = start + (count * _interval)
if self._skip and next_timer < monotonic():
count += 1
continue
try:

if await wait_for(_wait(), max(0, next_timer - monotonic())):
break
except TimeoutError:
Expand Down
4 changes: 2 additions & 2 deletions src/textual/_xterm_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def parse_mouse_code(self, code: str, sender: MessageTarget) -> events.Event | N
if sgr_match:
_buttons, _x, _y, state = sgr_match.groups()
buttons = int(_buttons)
button = buttons & 3
button = (buttons + 1) & 3
x = int(_x) - 1
y = int(_y) - 1
delta_x = x - self.last_x
Expand All @@ -41,7 +41,7 @@ def parse_mouse_code(self, code: str, sender: MessageTarget) -> events.Event | N
event: events.Event
if buttons & 64:
event = (
events.MouseScrollUp if button == 1 else events.MouseScrollDown
events.MouseScrollDown if button == 1 else events.MouseScrollUp
)(sender, x, y)
else:
event = (
Expand Down
Loading

0 comments on commit f3b8e7a

Please sign in to comment.