Cleanup easing example

This commit is contained in:
Darren Burns
2022-09-07 12:12:17 +01:00
parent a89835d554
commit b6e133c1af
3 changed files with 133 additions and 7 deletions

View File

@@ -5,10 +5,14 @@ from textual.widgets import Static
class JustABox(App):
css_path = "../darren/just_a_box.css"
def compose(self) -> ComposeResult:
yield Static("Hello, world!", classes="box1")
app = JustABox(watch_css=True)
if __name__ == "__main__":
app = JustABox(css_path="../darren/just_a_box.css", watch_css=True)
app.run()

View File

@@ -3,6 +3,38 @@ EasingButtons > Button {
}
EasingButtons {
dock: left;
width: 20;
overflow: auto auto;
width: 20;
}
#bar-container {
content-align: center middle;
}
#duration-input {
width: 30;
}
#inputs {
padding: 1;
height: auto;
dock: top;
}
Bar {
width: 1fr;
}
#other {
width: 1fr;
background: #555555;
padding: 1;
height: 100%;
border-left: vkey $background;
}
#opacity-widget {
padding: 1;
background: $panel;
border: wide #969696;
}

View File

@@ -1,20 +1,110 @@
from __future__ import annotations
from rich.console import RenderableType
from textual import layout
from textual._easing import EASING
from textual.app import ComposeResult, App
from textual.cli.previews.borders import TEXT
from textual.reactive import Reactive
from textual.scrollbar import ScrollBarRender
from textual.widget import Widget
from textual.widgets import Button, Static
from textual.widgets import Button, Static, Footer
from textual.widgets.text_input import TextWidgetBase, TextInput
VIRTUAL_SIZE = 100
WINDOW_SIZE = 10
START_POSITION = 0.0
END_POSITION = float(VIRTUAL_SIZE - WINDOW_SIZE)
class EasingButtons(Widget):
def compose(self) -> ComposeResult:
for easing in EASING:
yield Button(easing)
for easing in sorted([e for e in EASING if e != "round"], reverse=True):
yield Button(easing, id=easing)
class Bar(Widget):
position = Reactive.init(START_POSITION)
animation_running = Reactive(False)
def render(self) -> RenderableType:
return ScrollBarRender(
virtual_size=VIRTUAL_SIZE,
window_size=WINDOW_SIZE,
position=self.position,
style="green" if self.animation_running else "red",
)
class EasingApp(App):
position = Reactive.init(START_POSITION)
duration = Reactive.var(1.0)
def on_load(self):
self.bind(
"ctrl+p", "focus('duration-input')", description="Focus: Duration Input"
)
self.bind("ctrl+b", "toggle_dark", description="Toggle Dark")
def compose(self) -> ComposeResult:
self.animated_bar = Bar()
self.animated_bar.position = START_POSITION
duration_input = TextInput(
placeholder="Duration", initial="1.0", id="duration-input"
)
self.opacity_widget = Static(
f"[b]Welcome to Textual![/]\n\n{TEXT}", id="opacity-widget"
)
yield EasingButtons()
self.text = Static("Easing examples")
yield self.text
yield layout.Vertical(
layout.Vertical(Static("Animation Duration:"), duration_input, id="inputs"),
layout.Horizontal(
self.animated_bar,
layout.Container(self.opacity_widget, id="other"),
),
Footer(),
)
def on_button_pressed(self, event: Button.Pressed) -> None:
self.animated_bar.animation_running = True
def _animation_complete():
self.animated_bar.animation_running = False
target_position = (
END_POSITION if self.position == START_POSITION else START_POSITION
)
self.animate(
"position",
value=target_position,
final_value=target_position,
duration=self.duration,
easing=event.button.id,
on_complete=_animation_complete,
)
def watch_position(self, value: int):
self.animated_bar.position = value
self.opacity_widget.styles.opacity = 1 - value / END_POSITION
def on_text_widget_base_changed(self, event: TextWidgetBase.Changed):
if event.sender.id == "duration-input":
new_duration = _try_float(event.value)
if new_duration is not None:
self.duration = new_duration
def action_toggle_dark(self):
self.dark = not self.dark
def _try_float(string: str) -> float | None:
try:
return float(string)
except ValueError:
return None
app = EasingApp(css_path="easing.css", watch_css=True)